package game;

import game.resources.Parsing;
import game.resources.Profile;
import game.resources.ReverseIntegerComparator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Random;
import java.util.TreeMap;

import server.profiledGame;


public class GameLogic
{
    private GameData data; 
    
    public GameData getGameData() {
    	return data;
    }
    
    public void init(int gameID,List<Profile> profiles) {
    	data = new GameData(gameID);
		init(Parsing.toUsersIDMap(profiles));
		data.state = enumStates.INITIALIZATION;
		execute(data,null); //Optional
    }

    public void execute(GameData gameData,Map<String,String> parameters)  {
    	data = gameData;
    	switch(data.state)
    	{
    	case INITIALIZATION:
    		startGame();
    		break;
    	case MANAGE_PLACE_TILE_PRINT_DIALOG:
    		Coordinate newTileCoordinate = Parsing.toCoordinate(parameters);
    		if (newTileCoordinate == null)
    			endGame();
    		else
    			managePlaceTile2(false,newTileCoordinate);
    		break;
    	case MANAGE_BUY_STOCKS_PRINT_DIALOG:
    		manageBuyStocks(Parsing.toIntArray(parameters));
    		break;
    	case MANAGE_COMPANY_SELL_SAME_SIZED_DIALOG:
    		if (data.manageCompanySell_times != 2)
    			arrangeCompanySell(Parsing.toCompanySellOrderArray(parameters));
    		else
    			manageCompanySellContinue();
    		break;
    	case SELL_COMPANY_TO_COMPANY_GET_ORDER:
    			int[] numShares = Parsing.toTwoInts(parameters);
    			sellCompanyToCompany_SellOrder(numShares[0], numShares[1]);
    		break;
    	case START_GAME_PRINT_CHOOSE_COMPANY_DIALOG:
    		data.startGame_createdCompany = Parsing.toEnumCompany(parameters);
    		startGame();
    		break;
    	}
    	data.IncreaseActionCounter();
    	data.getLogWriter().postLogMessage();
    }
    
    //Create tiles, init companies, create players & init their money, etc.
    private void init(Map<String,Long> users)  
    {
    	
    	List<String> names = Parsing.toNameListOfStrings(users);
    	int numOfPlayers = names.size();
    	
        data.getCompanies()[0] = new CheapCompany(enumCompany.SACKSON); 
        data.getCompanies()[1] = new CheapCompany(enumCompany.ZETA); 
        data.getCompanies()[2] = new MediumCompany(enumCompany.HYDRA); 
        data.getCompanies()[3] = new MediumCompany(enumCompany.FUSION); 
        data.getCompanies()[4] = new MediumCompany(enumCompany.AMERICA); 
        data.getCompanies()[5] = new ExpensiveCompany(enumCompany.PHOENIX); 
        data.getCompanies()[6] = new ExpensiveCompany(enumCompany.QUANTUM); 

        for(int i=0; i<9; i++)
        {
            for(int j=0; j<12; j++)
        	{
                data.getTileList().add(new Tile(new Coordinate(i,j)));
            }
        }
        data.setBoard(new Board(data.getTileList()));  

        data.setPlayers(new Player[numOfPlayers]);     
        for (int i=0; i<numOfPlayers; i++)
        {
            data.getPlayers()[i] = null;
        }
        
        //get players names        
        int downCounter = numOfPlayers;
        String currentName;
        Random generator = new Random();
        int currentIndex = 0;
        int playerIndex;
        do 
        {
            playerIndex = generator.nextInt(data.getPlayers().length);
            if (data.getPlayers()[playerIndex] == null) 
            {
                currentName = names.get(currentIndex);
                data.getPlayers()[playerIndex] = new Player(currentName,users.get(currentName));
                data.getPlayers()[playerIndex].setIndex(playerIndex);
                data.getPlayers()[playerIndex].increaseCash(data.getInitialCash());
                downCounter--;
                currentIndex++;
            } 

        }
        
        while(downCounter > 0);
        data.setPlayerNames(new String[numOfPlayers]);
        for(int i = 0; i < numOfPlayers; i++){
        	data.getPlayerNames()[i] = data.getPlayers()[i].getName();
        }                
                
        placeStartTiles();
        
        for (int i=0; i < numOfPlayers; i++) {
            for (int j=0; j < data.getTilesPerPlayer(); j++) {
                data.getPlayers()[i].addTile(getNewTile());
            }
        }
        data.setCurrentPlayer(0);
        data.initLogWriter();
    }
    
    private void placeStartTiles() 
    {        
        for (int i=0; i<data.getPlayers().length; i++)
        {   
            Tile newTile = getNewTile();
            while (isCreateCompany(newTile))
            {
            	data.getTileList().add(newTile);
            	newTile = getNewTile();
            }
            
            newTile.setOnBoard();
        }
    }
    
    private Tile getNewTile()
    {
        Random generator = new Random();
        Tile newTile = null;
        if (data.getTileList().size() > 0)
        {
            newTile = data.getTileList().get(generator.nextInt(data.getTileList().size()));
            data.getTileList().remove(newTile);
        }
        return newTile;
    }
    
    private boolean isCreateCompany(Tile tile)
    { 
        boolean foundNonCompanyNeighbor = false;        
        Tile[] Neighbors = data.getBoard().getNeighbors(tile);        
        
        for (int i=0; i < 4; i++)
        {
        	if (Neighbors[i] != null)
        	{
        		if (Neighbors[i].isOnBoard() && Neighbors[i].getCompany() != null)
	            {
	            	return false;
	            }
        		
	            foundNonCompanyNeighbor = foundNonCompanyNeighbor || (Neighbors[i].isOnBoard() && (!Neighbors[i].isBetweenSafeCompanies()) && (Neighbors[i].getCompany() == null));	            
        	}
        }
               
        return (foundNonCompanyNeighbor);        
    }
    
    private void checkAndSetTileBetweenSafeCompanies(Tile tile) {                
        boolean foundFirstSafeCompanyNeighbor = false;        
        enumCompany firstSafeCompany = null;             
        Tile[] Neighbors = data.getBoard().getNeighbors(tile);
        
        for (int i=0; i < 4; i++)
        {
        	if (Neighbors[i] != null)
        	{        		
        		if (foundFirstSafeCompanyNeighbor)
	            {
        			if ((Neighbors[i].isOnBoard() && Neighbors[i].getCompany() != null && data.getCompanies()[Neighbors[i].getCompany().getIndex()].isSafe()
        				&& Neighbors[i].getCompany() != firstSafeCompany))
					{
        					tile.setBetweenSafeCompanies();
					}
	            }
        		else
        		{
        			foundFirstSafeCompanyNeighbor = (Neighbors[i].isOnBoard() && Neighbors[i].getCompany() != null && data.getCompanies()[Neighbors[i].getCompany().getIndex()].isSafe());
        			if (foundFirstSafeCompanyNeighbor)
		            {
		            	firstSafeCompany = Neighbors[i].getCompany();
		            }
        		}	            
        	}
        }       
    }
    
    private void managePlaceTile()  
    {   
        	if (!makeCertainPlayerHasAMove(data.getPlayers()[data.getCurrentPlayer()]))
        	{
        		if (data.managePlaceTile_increaseSkipped)
        		{
        			data.setSkippedTurns(data.getSkippedTurns() + 1);
        		}        		
        		data.getLogWriter().printLogMessage(1,data.getCurrentPlayer());        		
        		managePlaceTile2(true,null);
        	}        	 
    }

        
    private void managePlaceTile2(boolean b,Coordinate coordinate) {
    	data.managePlaceTile_chosenTileCoordinate = coordinate;

    	data.managePlaceTile_chosenTile = data.getBoard().getTile(data.managePlaceTile_chosenTileCoordinate);
                
    	data.managePlaceTile_tileCreateCompanyWhenAllOnBoard = (isCreateCompany(data.managePlaceTile_chosenTile) && data.getBoard().numOfCompaniesOnBoard() == data.getCompanies().length);
        if (data.managePlaceTile_tileCreateCompanyWhenAllOnBoard)
        {
        	data.getLogWriter().printLogPrivateMessage(data.getPlayers()[data.getCurrentPlayer()].getUserID(),15);
        }
        else
        {
        	checkAndSetTileBetweenSafeCompanies(data.managePlaceTile_chosenTile);
	        
	        if (data.managePlaceTile_chosenTile.isBetweenSafeCompanies())
	        {
	        	data.getPlayers()[data.getCurrentPlayer()].removeTile(data.managePlaceTile_chosenTile);	
	        	data.managePlaceTile_chosenTile.setOnBoard();
	        	data.getLogWriter().printLogPrivateMessage(data.getPlayers()[data.getCurrentPlayer()].getUserID(),2);
	        	
	        	if (data.getTileList().size() == 0 && data.managePlaceTile_firstTileBetweenSafeCompanies)
	        	{
	        		data.managePlaceTile_increaseSkipped = false;
	        	}
	        	
	        	data.managePlaceTile_firstTileBetweenSafeCompanies = false;
	        }
        }
    	if(((data.managePlaceTile_tileCreateCompanyWhenAllOnBoard || data.managePlaceTile_chosenTile.isBetweenSafeCompanies())) && (!b))
    	{
    		managePlaceTile();
    		return;
    	}
       
        if (data.managePlaceTile_chosenTile != null)
        {
        	data.managePlaceTile_chosenTile.setOnBoard();
	        data.getPlayers()[data.getCurrentPlayer()].removeTile(data.managePlaceTile_chosenTile);
        }
        else
        {
        	data.setSkippedTurns(0);
        }
        startGame();

    }
    
    //This function only checks if the player has a legal move - meaning that he has ANY tiles,
    //and that not all of his tiles create a company, when it's not allowed to create a company.    
    //notice: if some of the player's tiles are between safe companies, this function will return true.
    private boolean makeCertainPlayerHasAMove(Player player)
    {
    	if (data.getBoard().numOfCompaniesOnBoard() < data.getCompanies().length)
    	{
    		return true;
    	}
    	
    	boolean FoundPlaceableTile = false;
    	
    	for (Tile tile : player.getTiles())
    	{
    		if (!isCreateCompany(tile))
    		{
    			FoundPlaceableTile = true;
    			break;
    		}
    	}
    	
    	return FoundPlaceableTile;
    }
    
    private void connectTilesToCompany(Tile newlyPlacedTile)
    {   
        enumCompany neighborCompany = null;       
        int companySizeIncrement=0;
        Tile[] Neighbors = data.getBoard().getNeighbors(newlyPlacedTile);
        
        for (int i=0; i < 4; i++)
        {
        	if (Neighbors[i] != null)
        	{
        		if (Neighbors[i].isOnBoard() && Neighbors[i].getCompany() != null)
        		{
        			neighborCompany = Neighbors[i].getCompany();
        			break;
        		}
        	}
        }
        
        if (neighborCompany != null)
        {
        	newlyPlacedTile.setCompany(neighborCompany);
        	companySizeIncrement++;
        	
        	 for (int i=0; i < 4; i++)
             {
             	if (Neighbors[i] != null)
             	{
             		if (Neighbors[i].isOnBoard() && Neighbors[i].getCompany() == null && (!Neighbors[i].isBetweenSafeCompanies()))
             		{
             			Neighbors[i].setCompany(neighborCompany);
             			companySizeIncrement++;
             		}
             	}
             }
        	 
        	 data.getCompanies()[neighborCompany.getIndex()].increaseSize(companySizeIncrement);
        }
    }
    
    private boolean CreateCompany(Tile newlyPlacedTile, enumCompany createdCompany)
    {
    	data.getCompanies()[createdCompany.getIndex()].setCompanyOnBoard();
    	data.getBoard().setCompanyOnBoard(enumCompany.values()[createdCompany.getIndex()], true);
    	
    	Tile[] Neighbors = data.getBoard().getNeighbors(newlyPlacedTile);
    	
    	newlyPlacedTile.setCompany(createdCompany);
    	int newCompanySize = 1;
    	for (int i=0; i < 4; i++)
    	{
    		if (Neighbors[i] != null && Neighbors[i].isOnBoard() && (!Neighbors[i].isBetweenSafeCompanies()))
    		{
    			Neighbors[i].setCompany(createdCompany);
    			newCompanySize++;
    		}
    	}
    	
    	data.getCompanies()[createdCompany.getIndex()].increaseSize(newCompanySize);
    	
    	if (data.getCompanies()[createdCompany.getIndex()].remainingStocksInBank() > 0)
    	{
	    	data.getCompanies()[createdCompany.getIndex()].decreaseStocksInBank(1);
	    	data.getPlayers()[data.getCurrentPlayer()].increaseCompanyStock(createdCompany,1);
	    	return true;
    	}
    	
    	return false;
    }
    	
    
    private void manageBuyStocks(int[] stocksToBuy) {
    	    	
    	boolean repeatBuyingProcess = false;

    	int playerCash = data.getPlayers()[data.getCurrentPlayer()].getCash();
    	
		for (int i=0; i < 7; i++)
		{
			if (data.getCompanies()[i].remainingStocksInBank() < stocksToBuy[i])
			{
				repeatBuyingProcess = true;
				data.getLogWriter().printLogPrivateMessage(data.getPlayers()[data.getCurrentPlayer()].getUserID(),10,data.getCompanies()[i].remainingStocksInBank(),i);
			}
			
			if (stocksToBuy[i] > 0)
			{
				playerCash -= stocksToBuy[i] * data.getCompanies()[i].getStockValue();
			}
		}
		
		if (playerCash < 0)
		{
			repeatBuyingProcess = true;
			data.getLogWriter().printLogPrivateMessage(data.getPlayers()[data.getCurrentPlayer()].getUserID(),6,(-1*playerCash));
		}
	
		if (repeatBuyingProcess)
		{
			return;
		}
    	
    	
    	/*execute transaction*/
    	int playerCashBeforeTransaction = data.getPlayers()[data.getCurrentPlayer()].getCash();
    	boolean isNonTrivialTransaction = false;
    	
    	for (int i=0; i < 7; i++)
    	{
    		if (stocksToBuy[i] > 0)
    		{
    			isNonTrivialTransaction = true;
    			
    			data.getPlayers()[data.getCurrentPlayer()].decreaseCash(stocksToBuy[i] * data.getCompanies()[i].getStockValue());
    			data.getPlayers()[data.getCurrentPlayer()].increaseCompanyStock(enumCompany.values()[i], stocksToBuy[i]);
    			data.getCompanies()[i].decreaseStocksInBank(stocksToBuy[i]); 
    			
    			data.getLogWriter().printLogMessage(31, data.getCurrentPlayer(), stocksToBuy[i], i);
    		}
    	}
    	
    	if (isNonTrivialTransaction)
    	{
    		data.getLogWriter().printLogPrivateMessage(data.getPlayers()[data.getCurrentPlayer()].getUserID(),5,(playerCashBeforeTransaction - data.getPlayers()[data.getCurrentPlayer()].getCash()));
    	}
    	else {
    		//prints to all player did not buy any stocks
    		data.getLogWriter().printLogMessage(34, data.getCurrentPlayer());
    	}
    	
    	startGame();
    }
    
    /*this function checks whether the newlyPlacedTile is in between of two companies.
     *we assume the new tile is NOT between two-or-more SAFE companies.*/
    private boolean isCompanySell(Tile newlyPlacedTile)
    {
    	Tile[] neighbors = data.getBoard().getNeighbors(newlyPlacedTile);
    	boolean foundFirstCompany = false;
    	enumCompany firstCompany = null;
    	
    	for (int i=0; i < 4; i++)
    	{
    		if (neighbors[i] != null)
    		{
    			if (foundFirstCompany)
    			{
    				if (neighbors[i].isOnBoard() && neighbors[i].getCompany() != null
    					&& neighbors[i].getCompany() != firstCompany)
    				{
    					return true;
    				}
    			}
    			else
    			{
	    			if (neighbors[i].isOnBoard() && neighbors[i].getCompany() != null)
	    			{
	    				foundFirstCompany = true;
	    				firstCompany = neighbors[i].getCompany();
	    			}
    			}
    		}
    	}
    	
    	return false;
    }
    
    private void manageCompanySell(Tile newlyPlacedTile)  
    {
        data.manageCompanySell_neighbors = data.getBoard().getNeighbors(newlyPlacedTile);     
        data.manageCompanySell_neighborCompanies = new HashMap<Integer, List<Company>>();            
        data.manageCompanySell_numOfNeighborCompanies = 0;
        data.manageCompanySellContinue_OuterIndex = 0;
		data.manageCompanySellContinue_InnerIndex = 0;
        for (int i = 0; i < 4; i++)
        {
            if (data.manageCompanySell_neighbors[i] != null && data.manageCompanySell_neighbors[i].getCompany() != null)
            {
                Company curCompany = data.getCompanies()[data.manageCompanySell_neighbors[i].getCompany().getIndex()];
                int companySize = curCompany.getSize();
                
                if (!data.manageCompanySell_neighborCompanies.containsKey(companySize))
                {
                    List<Company> compOfSpecificSize = new ArrayList<Company>();
                    compOfSpecificSize.add(curCompany);
                    data.manageCompanySell_neighborCompanies.put(companySize,compOfSpecificSize);
                    data.manageCompanySell_numOfNeighborCompanies++;
                }
                else if (!data.manageCompanySell_neighborCompanies.get(companySize).contains(curCompany))
                {                        
                	data.manageCompanySell_neighborCompanies.get(companySize).add(curCompany);
                	data.manageCompanySell_numOfNeighborCompanies++;
                }
            }
        }

        data.manageCompanySell_neighborCompanies = new TreeMap<Integer, List<Company>>(data.manageCompanySell_neighborCompanies);
        data.manageCompanySell_buyingCompany = null;  
        data.manageCompanySell_times = 2;
        
        for (Map.Entry<Integer, List<Company>> curEntry : data.manageCompanySell_neighborCompanies.entrySet())
        {  
            if (curEntry.getValue().size() > 1)
            {
            	data.manageCompanySell_times--;
            	if (data.manageCompanySell_times == 1) {
            		data.manageCompanySell_SameSized1 = curEntry.getValue();
            	} else {
            		data.manageCompanySell_SameSized2 = curEntry.getValue();
            	}
            }
        }
        data.managePlaceTile_NewlyPlacedTile = newlyPlacedTile;
        
        if (data.manageCompanySell_times != 2) {
        	data.state = enumStates.MANAGE_COMPANY_SELL_SAME_SIZED_DIALOG;        	
        } else {
        	setBuyingCompany();
        	manageCompanySellContinue();
        }
    }
    
    private void setBuyingCompany() {
    	int max = 0;
    	for (Map.Entry<Integer, List<Company>> curEntry : data.manageCompanySell_neighborCompanies.entrySet())
    	{
    		if (curEntry.getKey() > max)
    		{
    			data.manageCompanySell_buyingCompany = curEntry.getValue().get(curEntry.getValue().size()-1);
    			max = curEntry.getKey();
    		}
    	}
    }
    
    private List<Company> getReleventSameSized() {
    	if (data.manageCompanySell_times == 1) {
    		return data.manageCompanySell_SameSized1;
    	} else {	
    		return data.manageCompanySell_SameSized2;
    	}
    }
    
    private void arrangeCompanySell(String[] CompanyIndices) 
    {
    	Queue<Company> newOrder = new LinkedList<Company>();
    	
        for (int i=0; i < CompanyIndices.length; i++)
        {
        	for (int j=0; j < data.getCompanies().length; j++)
        	{
        		if (data.getCompanies()[j].toString().equals(CompanyIndices[i]))
        		{
        			newOrder.add(data.getCompanies()[j]);
        			getReleventSameSized().remove(data.getCompanies()[j]);
        			break;
        		}
        	}
        }
        // because CompanyIndices doesn't contain the last company:
        newOrder.add(getReleventSameSized().get(0));
        getReleventSameSized().clear();
   
        while (!newOrder.isEmpty()) {
        	getReleventSameSized().add(newOrder.poll());
        }
          
    	data.manageCompanySell_times++;
    	if (data.manageCompanySell_times == 2) {
    		setBuyingCompany();
    		manageCompanySellContinue();
    	}
    }

	private void manageCompanySellContinue()  {
		
		Tile newlyPlacedTile = data.managePlaceTile_NewlyPlacedTile;
		// buying company update
		
		if (data.manageCompanySellContinue_OuterIndex != data.manageCompanySell_neighborCompanies.values().size())
		{
	        int j=0;
	        List<Company> temp = null;
			for (List<Company> specificSizeCompanySet : data.manageCompanySell_neighborCompanies.values())
	        {
				if (j >= data.manageCompanySellContinue_OuterIndex) {
					temp = specificSizeCompanySet;
					break;
				}
				j++;
	        }
			Company temp2 = temp.get(data.manageCompanySellContinue_InnerIndex);		
			
			data.manageCompanySellContinue_InnerIndex++;
			if (data.manageCompanySellContinue_InnerIndex == temp.size())
			{
				data.manageCompanySellContinue_OuterIndex++;
				data.manageCompanySellContinue_InnerIndex = 0;
			}
			
			if (!temp2.toString().equals(data.manageCompanySell_buyingCompany.toString()))
			{
				data.sellCompanyToCompany_ReturnToEnd = false;
				sellCompanyToCompany(temp2,data.manageCompanySell_buyingCompany);				
			}
			else
			{
				manageCompanySellContinue();
			}
			return;
		}

        //setting the placed tile to be part of the buying company
		newlyPlacedTile.setCompany(enumCompany.values()[data.manageCompanySell_buyingCompany.getCompanyIndex()]);
		data.manageCompanySell_buyingCompany.increaseSize(1);
		//setting any nearby tiles which also got connected by the placed tile to be part of the buying company
		for (int i = 0; i < 4; i++)
		{
			if (data.manageCompanySell_neighbors[i] != null && data.manageCompanySell_neighbors[i].isOnBoard() && data.manageCompanySell_neighbors[i].getCompany() == null)
            {
				data.manageCompanySell_neighbors[i].setCompany(enumCompany.values()[data.manageCompanySell_buyingCompany.getCompanyIndex()]);
				data.manageCompanySell_buyingCompany.increaseSize(1);
            }
		}
		
		data.manageCompanySell_SameSized1 = null;
		data.manageCompanySell_SameSized2 = null;
		
		data.state = enumStates.START_GAME_MANAGE_COMPANY_SELL;
		startGame();
	} 
    
    private void sellCompanyToCompany(Company soldCompany, Company buyingCompany) 
    {    	    	   	
    	enumCompany soldCompanyEnum = enumCompany.values()[soldCompany.getCompanyIndex()];
    	enumCompany buyingCompanyEnum = null;    	
    	
    	//Calculate prizes.
    	List<Player> topShareHolders = new ArrayList<Player>();
    	List<Player> secondShareHolders = new ArrayList<Player>();
    	int mostShares = 0;
    	int secondMostShares = 0;
    	
    	for (Player player : data.getPlayers())
    	{
    		int numOfPlayerShares = player.getCompanyStocks(soldCompanyEnum);
    		
    		if (numOfPlayerShares > mostShares)
    		{
    			topShareHolders.clear();
    			topShareHolders.add(player);
    			mostShares = numOfPlayerShares;
    		}
    		
    		else if (numOfPlayerShares > 0 && numOfPlayerShares == mostShares)
    		{
    			topShareHolders.add(player);
    		}    		
    	}
    	
    	if (topShareHolders.size() < 2)
    	{
    		for (Player player : data.getPlayers())
        	{
    			if (!topShareHolders.contains(player))
    			{
	        		int numOfPlayerShares = player.getCompanyStocks(soldCompanyEnum);
	        		
	        		if (numOfPlayerShares > secondMostShares)
	        		{
	        			secondShareHolders.clear();
	        			secondShareHolders.add(player);
	        			secondMostShares = numOfPlayerShares;
	        		}
	        		
	        		else if (numOfPlayerShares > 0 && numOfPlayerShares == secondMostShares)
	        		{
	        			secondShareHolders.add(player);
	        		}    
    			}
        	}
    	}
    	
    	//Give out prizes.
    	int prizeMoney = 0;
    	int secondPrizeMoney = 0;
    	if (secondShareHolders.size() == 0) //occurs when either only 1 share holder, or when there is a tie for first place
    	{
    		prizeMoney = (soldCompany.getFirstPrizeValue() + soldCompany.getSecondPrizeValue()) / topShareHolders.size();
    		 
    		for (Player player : topShareHolders)
    		{
    			player.increaseCash(prizeMoney);
    		}
    	}    	
    	else
    	{
    		prizeMoney = soldCompany.getFirstPrizeValue() / topShareHolders.size();
    		
    		topShareHolders.get(0).increaseCash(soldCompany.getFirstPrizeValue());
    		
    		secondPrizeMoney = soldCompany.getSecondPrizeValue() / secondShareHolders.size();
   		 
    		for (Player player : secondShareHolders)
    		{
    			player.increaseCash(secondPrizeMoney);
    		}
    	}
    	
    	//needs to print "company A sold to company B", all players' stocks of company A, and who gets prizes and how much..
    	//also needs to check if buyingCompany is null, if so then print "selling to bank" or something..
    	printSellCompanyDialog(soldCompany, buyingCompany, topShareHolders, 
                secondShareHolders, prizeMoney, secondPrizeMoney, data.getPlayers());
    	
    	
    	int playerIndex = data.getCurrentPlayer();    	
    	boolean firstTime = true;
    	
    	if (buyingCompany != null) //(buyingCompany == null) occurs at end-game.
    	{
    		buyingCompanyEnum = enumCompany.values()[buyingCompany.getCompanyIndex()];
    		
    		//go over all players, starting with the one who caused the merger, then continplayers[currentPlayer].getUI()ng "clock-wise". stop upon returning to merger-causer.
    		data.SellOrder_soldCompany = soldCompany;
    	    data.SellOrder_buyingCompany = buyingCompany;
    	    data.SellOrder_soldCompanyEnum = soldCompanyEnum;
    	    data.SellOrder_buyingCompanyEnum = buyingCompanyEnum;
    	    data.setSellOrder_playerIndex(playerIndex);
    	    data.SellOrder_firstTime = firstTime;
    	    data.state = enumStates.SELL_COMPANY_TO_COMPANY_GET_ORDER;
    	}
    	else //means we're at end-game.
    	{
    		//go over all players and sell all of their soldCompany shares.
    		for (int i = 0; i < data.getPlayers().length; i++)
	    	{
	    		int curPlayerShares = data.getPlayers()[i].getCompanyStocks(soldCompanyEnum);
	    		
	    		if (curPlayerShares > 0) //if the player has shares of the sold company
	    		{	
		    		int moneyIncrease = curPlayerShares * soldCompany.getStockValue();
		    		data.getPlayers()[i].decreaseCompanyStock(soldCompanyEnum, curPlayerShares);
		    		data.getPlayers()[i].increaseCash(moneyIncrease);
		    		data.getLogWriter().printLogMessage(14,i, curPlayerShares, moneyIncrease);
	    		}
	    	}
    		sellCompanyToCompanyContinue(soldCompany, buyingCompany,
    				soldCompanyEnum, buyingCompanyEnum);	
    	}
	}


	private void sellCompanyToCompany_SellOrder(int numOfSharesToSell,int numOfSharesToConvert)  {
		// if not enough stocks left to convert to:
		if (data.SellOrder_buyingCompany.remainingStocksInBank() < numOfSharesToConvert/2)
		{
			data.getLogWriter().printLogPrivateMessage(data.getPlayers()[data.getSellOrder_playerIndex()].getUserID(),9
					,data.SellOrder_buyingCompany.remainingStocksInBank(),data.SellOrder_buyingCompany.companyIndex);
			return;
		}
		
		data.SellOrder_firstTime = false;
		int curPlayerShares = data.getPlayers()[data.getSellOrder_playerIndex()].getCompanyStocks(data.SellOrder_soldCompanyEnum);

		if (curPlayerShares > 0) //if the player has shares of the sold company
		{
			//Sell soldCompany shares
			if (numOfSharesToSell > 0)
			{
				int moneyIncrease = numOfSharesToSell * data.SellOrder_soldCompany.getStockValue();
				data.getPlayers()[data.getSellOrder_playerIndex()].decreaseCompanyStock(data.SellOrder_soldCompanyEnum, numOfSharesToSell);
				data.getPlayers()[data.getSellOrder_playerIndex()].increaseCash(moneyIncrease);
				data.getLogWriter().printLogMessage(11,data.getSellOrder_playerIndex(), numOfSharesToSell, moneyIncrease);
				curPlayerShares -= numOfSharesToSell;
				data.SellOrder_soldCompany.increaseStocksInBank(numOfSharesToSell);
			}

			//Convert soldCompany shares
		
			if (numOfSharesToConvert > 0)
			{
				data.getPlayers()[data.getSellOrder_playerIndex()].decreaseCompanyStock(data.SellOrder_soldCompanyEnum, numOfSharesToConvert);
				data.getPlayers()[data.getSellOrder_playerIndex()].increaseCompanyStock(data.SellOrder_buyingCompanyEnum, numOfSharesToConvert/2);
				data.getLogWriter().printLogMessage(12,data.getSellOrder_playerIndex(), numOfSharesToConvert, numOfSharesToConvert/2, data.SellOrder_buyingCompany.getCompanyIndex());

				curPlayerShares -= numOfSharesToConvert;
				data.SellOrder_buyingCompany.decreaseStocksInBank(numOfSharesToConvert/2);
				data.SellOrder_soldCompany.increaseStocksInBank(numOfSharesToConvert);
			}
			
			data.getLogWriter().printLogMessage(13, data.getSellOrder_playerIndex(), curPlayerShares);
		}

		data.setSellOrder_playerIndex((data.getSellOrder_playerIndex()+1) % data.getPlayers().length);
		
        if (!(data.SellOrder_firstTime || (data.getSellOrder_playerIndex() != data.getCurrentPlayer())))
        {
            sellCompanyToCompanyContinue(data.SellOrder_soldCompany,
                    data.SellOrder_buyingCompany,
                    data.SellOrder_soldCompanyEnum,
                    data.SellOrder_buyingCompanyEnum);
        }
		
	}
	
	private void sellCompanyToCompanyContinue(Company soldCompany,
			Company buyingCompany, enumCompany soldCompanyEnum,
			enumCompany buyingCompanyEnum)  {
		//go over ALL TILES ON BOARD and set the ones who are soldCompany to be buyingCompany.
    	for (int i=0; i < 9; i++)
    	{
    		for (int j=0; j < 12; j++)
    		{
    			Tile tile = data.getBoard().getTile(i,j);
    			if (tile.getCompany() == soldCompanyEnum)
    			{
    				tile.setCompany(buyingCompanyEnum);
    			}
    		}    		
    	}

        if (buyingCompany != null)
        {
           buyingCompany.increaseSize(soldCompany.getSize());
        }
        
        soldCompany.removeFromBoard();
        data.getBoard().setCompanyOnBoard(soldCompanyEnum, false);
        
        if (!data.sellCompanyToCompany_ReturnToEnd)
        {
        	manageCompanySellContinue();
        }        
	}


	private void startGame()  
    {
		switch(data.state) 
		{
		case INITIALIZATION:
		case START_GAME_LOOP:
			data.managePlaceTile_chosenTile = null;	
			data.getLogWriter().printLogMessage(30, data.getCurrentPlayer()); 
			
			data.state = enumStates.MANAGE_PLACE_TILE_PRINT_DIALOG;
			managePlaceTile();
			break;
		
		case MANAGE_PLACE_TILE_PRINT_DIALOG:
			if (data.managePlaceTile_chosenTile != null)
			{
				data.startGame_IsCreatedCompany = false;
				data.startGame_createdCompany = null;
				if (isCreateCompany(data.managePlaceTile_chosenTile))
				{					
					data.startGame_IsCreatedCompany = true;
					data.state = enumStates.START_GAME_PRINT_CHOOSE_COMPANY_DIALOG;
					break;
				}
			}
			
		case START_GAME_PRINT_CHOOSE_COMPANY_DIALOG:				
			if (data.managePlaceTile_chosenTile != null)
			{
				if (data.startGame_IsCreatedCompany && !CreateCompany(data.managePlaceTile_chosenTile, data.startGame_createdCompany))
				{
					data.getLogWriter().printLogMessage(3);
				}

				if(!data.startGame_IsCreatedCompany && isCompanySell(data.managePlaceTile_chosenTile))
				{
					manageCompanySell(data.managePlaceTile_chosenTile);
					break;
				}
		
				else if (!data.startGame_IsCreatedCompany)
				{
					connectTilesToCompany(data.managePlaceTile_chosenTile);
				}
			}
		case START_GAME_MANAGE_COMPANY_SELL: 
			if (data.managePlaceTile_chosenTile != null)
			{
				data.getLogWriter().printLogMessage(32, data.getCurrentPlayer(), data.managePlaceTile_chosenTile.getLocation().getX(), data.managePlaceTile_chosenTile.getLocation().getY());
				if (data.startGame_createdCompany != null) {
					data.getLogWriter().printLogMessage(35,data.startGame_createdCompany.getIndex());
				}			
			} 

			else {
				data.getLogWriter().printLogMessage(33, data.getCurrentPlayer());
			}


			if (data.getBoard().numOfCompaniesOnBoard() > 0)
			{                    
				data.state = enumStates.MANAGE_BUY_STOCKS_PRINT_DIALOG;
				break;
			}
			
		case MANAGE_BUY_STOCKS_PRINT_DIALOG:
			
			while (data.getPlayers()[data.getCurrentPlayer()].getTiles().size() < data.getTilesPerPlayer())
			{
				Tile newTile = getNewTile();
				if (newTile != null)
				{
					data.getPlayers()[data.getCurrentPlayer()].addTile(newTile); //was getNewTile()
				}
				else
				{
					data.getLogWriter().printLogMessage(4);
					break;
				}
			}

			data.setCurrentPlayer((data.getCurrentPlayer() + 1)
					% data.getPlayers().length);    


			if (!isGameOver())
			{
				data.state = enumStates.START_GAME_LOOP;
				startGame();
			}
			else 
				endGame();
		}
    }
    
	private void calcEndGameResults() 
    {
        ReverseIntegerComparator comparator = new ReverseIntegerComparator();
        
    	data.setFinalStandings(new TreeMap<Integer,List<Player>>(comparator));
    		
        for (Player player : data.getPlayers()) {
        	int curPlayerCash = player.getCash();
        	
        	if (data.getFinalStandings().containsKey(curPlayerCash)) {
        		data.getFinalStandings().get(curPlayerCash).add(player);
        	} else {
        		List<Player> sameCashList = new ArrayList<Player>();
        		sameCashList.add(player);
        		data.getFinalStandings().put(curPlayerCash, sameCashList);
        	}
        }
    }

    private void sellAllRemainingCompanies()  
    {
    	//go over all companies and sell all the ones on board to the bank.
        for (Company company : data.getCompanies())
        {
        	if (company.isOnBoard())
        	{
        		data.sellCompanyToCompany_ReturnToEnd = true;
        		sellCompanyToCompany(company, null);
        	}
        }        
    }

    //checks for 3 11+ companies, 1 41+ company.
    private boolean isGameOver()
    {
        if (data.getSkippedTurns() == data.getPlayers().length)
        {
        	return true;
        }
    	
    	int counter = 0;                
        
        for (int i=0; i < 7; i++) {
            if (data.getCompanies()[i].getSize() >= 41) {
                return true;
            } else if (data.getCompanies()[i].getSize() >= 11) {
                counter++;
            }
        }
        
        if (counter >= 3) {
            return true;
        }
        
        return false;
    }
    
    private void printSellCompanyDialog(Company soldCompany, Company buyingCompany, List<Player> topShareHolders, 
            List<Player> secondShareHolders, int prizeMoney, int secondprizeMoney, Player[] players)
    {
        
        ShareSizeComparator comparator = new ShareSizeComparator(enumCompany.values()[soldCompany.getCompanyIndex()]);

        if (buyingCompany == null) {
        	data.getLogWriter().printLogMessage(16, soldCompany.getCompanyIndex());
        } else {
        	data.getLogWriter().printLogMessage(17, soldCompany.getCompanyIndex(), buyingCompany.getCompanyIndex());
        }

        data.getLogWriter().printLogMessage(18);
        for (Player shareHolder : topShareHolders) {
        	data.getLogWriter().printLogMessage(19, shareHolder.getIndex(), prizeMoney);
        }

        if (secondShareHolders.size() > 0) {
        	data.getLogWriter().printLogMessage(20);  
        	for (Player shareHolder : secondShareHolders) {
        		data.getLogWriter().printLogMessage(21, shareHolder.getIndex(),secondprizeMoney);
        	}
        }
  
        int counter = 0;
        for (int i=0; i<players.length; i++) {
            if (players[i].getCompanyStocks(enumCompany.values()[soldCompany.getCompanyIndex()]) > 0) {
                counter++;
            }
        }
        
        Player shareHolders[] = new Player[counter];
        
        int j = 0;
        for (int i=0; i<players.length; i++) {
            if (players[i].getCompanyStocks(enumCompany.values()[soldCompany.getCompanyIndex()]) > 0) {
                shareHolders[j] = players[i];
                j++;
            }
        }
        
        Arrays.sort(shareHolders, comparator);

        data.getLogWriter().printLogMessage(22);
        for (Player shareHolder : shareHolders) {
        	data.getLogWriter().printLogMessage(23,shareHolder.getIndex(),
        			shareHolder.getCompanyStocks(enumCompany.values()[soldCompany.getCompanyIndex()])
        			,soldCompany.getCompanyIndex());
        }
    }
    
    void endGame()  {
    	  sellAllRemainingCompanies();
          calcEndGameResults();
         
          List<Player> clientFinalStandings = new ArrayList<Player>();
          for (Integer currentCash : data.getFinalStandings().keySet()) {
              for (Player p : data.getFinalStandings().get(currentCash)) {
                  clientFinalStandings.add(p);
              }
          }
          data.setClientFinalStandings(clientFinalStandings);
         
          profiledGame.updateProfilesRanking(data.getFinalStandings());
          data.state = enumStates.GAME_ENDED;
    }
    
    class ShareSizeComparator implements Comparator<Player> {

        enumCompany company;
        
        public ShareSizeComparator(enumCompany company) {
            this.company = company;
        }
        
        public int compare(Player player1, Player player2) {
            return player2.getCompanyStocks(company) - player1.getCompanyStocks(company);
        }
    }
   
}